﻿using NAudio.CoreAudioApi;
using NAudio.Wave;
using NAudio.Wave.Compression;
using NAudio.Wave.SampleProviders;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

namespace PracticalExamples.Util
{
    class NAudioRecorder
    {
        public WaveIn waveSource = null;
        public WaveFileWriter waveFile = null;

        AcmStream resampleStream = null;
        public WasapiLoopbackCapture capture = null;
        private string fileName = string.Empty;
        FileStream fsOut;
        [DllImport("iMobie.AudioDriverInterface.dll", CallingConvention = CallingConvention.Cdecl)]
        static extern void SendToMic(byte[] buffer, ulong lenth);
        [DllImport("iMobie.AudioDriverInterface.dll", CallingConvention = CallingConvention.Cdecl)]
        static extern int Init();


        /// <summary>
        /// 第二步：开始录音
        /// </summary>
        public void StartRec()
        {
            try
            {
                waveSource = new WaveIn();//保证电脑有麦克接入否则报错。
                waveSource.WaveFormat = new WaveFormat(44100, 16, 2); // 16KHz，16bit,单声道的录音格式
				waveSource.BufferMilliseconds = 100;
				waveSource.NumberOfBuffers = 3;
                waveSource.DataAvailable += new EventHandler<WaveInEventArgs>(waveSource_DataAvailable);
                waveSource.RecordingStopped += new EventHandler<StoppedEventArgs>(waveSource_RecordingStopped);

                //waveFile = new WaveFileWriter(fileName, waveSource.WaveFormat);
                fsOut = new FileStream(fileName, FileMode.Create);

                waveSource.StartRecording();
                if (Init() != 1)
                {
                    throw new ApplicationException("没有初始化音频设备");
                }
            }
            catch (Exception e)
            {
                throw new Exception(e.Message);
            }

        }

        /// <summary>
        /// 第三步：停止录音
        /// </summary>
        public void StopRec()
        {
            waveSource.StopRecording();

            // Close Wave(Not needed under synchronous situation)
            if (waveSource != null)
            {
                waveSource.Dispose();
                waveSource = null;
            }

            if (waveFile != null)
            {
                waveFile.Dispose();
                waveFile = null;
            }

            if (fsOut != null)
            {
                fsOut.Close();
                fsOut.Dispose();
                fsOut = null;
            }
        }

        /// <summary>
        /// 第一步：设置录音结束后保存的文件路径
        /// </summary>
        /// <param name="fileName">保存wav文件的路径名</param>
        public void SetFileName(string fileName)
        {
            this.fileName = fileName;
        }

        /// <summary>
        /// 开始录音回调函数
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void waveSource_DataAvailable(object sender, WaveInEventArgs e)
        {
            /*if (waveFile != null)
            {
                waveFile.Write(e.Buffer, 0, e.BytesRecorded);
                waveFile.Flush();
            }*/
            //using (FileStream fsIn = new FileStream(fileName, FileMode.Open))
            byte[] buffer = new byte[e.Buffer.Length];
            Array.Copy(e.Buffer, buffer, e.Buffer.Length);
            /*for (int i = 0; i < buffer.Length; i = i + 2)
            {
                short s = BitConverter.ToInt16(buffer, i);
                int t = s << 2;//4倍
                if (t < short.MinValue)
                {
                    t = short.MinValue;
                }
                if (t > short.MaxValue)
                {
                    t = short.MaxValue;
                }
                var b = BitConverter.GetBytes(t);
                buffer[i] = b[0];
                buffer[i + 1] = b[1];
            }*/

            SendToMic(buffer, (ulong)buffer.Length);
            fsOut.Write(e.Buffer, 0, e.Buffer.Length);
            fsOut.Flush();
        }

        /// <summary>
        /// 录音结束回调函数
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void waveSource_RecordingStopped(object sender, StoppedEventArgs e)
        {
            if (waveSource != null)
            {
                waveSource.Dispose();
                waveSource = null;
            }

            if (waveFile != null)
            {
                waveFile.Dispose();
                waveFile = null;
            }
            if (fsOut != null)
            {
                fsOut.Close();
                fsOut.Dispose();
                fsOut = null;
            }

        }

        public int GetCurrentMicVolume()
        {
            int volume = 0;
            var enumerator = new MMDeviceEnumerator();

            //获取音频输入设备
            IEnumerable<MMDevice> captureDevices = enumerator.EnumerateAudioEndPoints(DataFlow.Capture, DeviceState.Active).ToArray();
            if (captureDevices.Count() > 0)
            {
                MMDevice mMDevice = captureDevices.ToList()[0];
                volume = (int)(mMDevice.AudioEndpointVolume.MasterVolumeLevelScalar * 100);
            }
            return volume;
        }

        public void SetCurrentMicVolume(int volume)
        {
            var enumerator = new MMDeviceEnumerator();
            IEnumerable<MMDevice> captureDevices = enumerator.EnumerateAudioEndPoints(DataFlow.Capture, DeviceState.Active).ToArray();
            if (captureDevices.Count() > 0)
            {
                MMDevice mMDevice = captureDevices.ToList()[0];
                mMDevice.AudioEndpointVolume.MasterVolumeLevelScalar = volume / 100.0f;
            }
        }
		WdlResampler resampler = new WdlResampler();
		NAudio.Wave.WaveFileWriter waveWriter;
		public void StartSpeaker()
        {
			//var enumerator = new MMDeviceEnumerator();
			//MMDevice mMDevice = enumerator.GetDefaultAudioEndpoint(DataFlow.Capture, Role.Multimedia);

			fsOut = new FileStream(fileName, FileMode.Create);
			capture = new WasapiLoopbackCapture();
            //capture.WaveFormat = new WaveFormat(44100, 16, 2);//只能重新采样。
            capture.DataAvailable += new EventHandler<WaveInEventArgs>(GetSpeakerDate);
            capture.RecordingStopped += (s, e) =>
            {
                fsOut.Close();
                fsOut.Dispose();
                fsOut = null;
                capture.Dispose();
            };
			
			capture.StartRecording();
            if (Init() != 1)
            {
                Console.WriteLine("初始化失败！");
            }
        }
		int countBytes = 0;
		byte[] AudioBuffer = new byte[2000000];

		private void GetSpeakerDate(object sender, WaveInEventArgs e)
        {

			byte[] output = Convert16(e.Buffer, e.BytesRecorded, capture.WaveFormat);
			int tmp = countBytes + output.Length;
			if (tmp > AudioBuffer.Length)
			{
				Array.Resize(ref AudioBuffer, AudioBuffer.Length * 2);
			}
			Buffer.BlockCopy(output, 0, AudioBuffer, countBytes, output.Length);
			
			SendToMic(output, (ulong)output.Length);
			//fsOut.Write(AudioBuffer, countBytes, output.Length);
			countBytes += output.Length;
		}
        public void StopSpeaker()
        {
            capture.StopRecording();
        }

		public byte[] Convert16(byte[] input, int length, WaveFormat format)
		{
			if (length == 0)
				return new byte[0];
			using (var memStream = new MemoryStream(input, 0, length))
			{
				using (var inputStream = new RawSourceWaveStream(memStream, format))
				{
					var sampleStream = new WaveToSampleProvider(inputStream);
					var resamplingProvider = new NAudio.Wave.SampleProviders.WdlResamplingSampleProvider(sampleStream, 8000);
					var ieeeToPCM = new SampleToWaveProvider16(resamplingProvider);
					var sampleStreams = new StereoToMonoProvider16(ieeeToPCM);
					sampleStreams.RightVolume = 0.5f;
					sampleStreams.LeftVolume = 0.5f;
					return readStream(sampleStreams, length);
				}
			}
		}

		private byte[] readStream(IWaveProvider waveStream, int length)
		{
			byte[] buffer = new byte[length];
			using (var stream = new MemoryStream())
			{
				int read;
				while ((read = waveStream.Read(buffer, 0, length)) > 0)
				{
					stream.Write(buffer, 0, read);
				}
				return stream.ToArray();
			}
		}

		static byte[] Float2Byte(float[] f)
        {
			GCHandle unmanaged_data_handle = GCHandle.Alloc(f, GCHandleType.Pinned);
			var ptr = unmanaged_data_handle.AddrOfPinnedObject();
			int length = f.Length * sizeof(float);

			byte[] bData = new byte[length];
			Marshal.Copy(ptr, bData, 0, length);

			unmanaged_data_handle.Free();
			return bData;

		}

	}
}


class VolumeInfo : System.ComponentModel.INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private int _volume;

    public int Volume
    {
        get { return _volume; }
        set {
            _volume = value;
            if (PropertyChanged!=null)
            {
                PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Volume"));
            }
        }
    }
}

/// <summary>
/// Fully managed resampling sample provider, based on the WDL Resampler
/// </summary>
public class WdlResamplingSampleProvider : ISampleProvider
{
    private readonly WdlResampler resampler;
    private readonly WaveFormat outFormat;
    private readonly ISampleProvider source;
    private readonly int channels;

    /// <summary>
    /// Constructs a new resampler
    /// </summary>
    /// <param name="source">Source to resample</param>
    /// <param name="newSampleRate">Desired output sample rate</param>
    public WdlResamplingSampleProvider(ISampleProvider source, int newSampleRate)
    {
        channels = source.WaveFormat.Channels;
        outFormat = WaveFormat.CreateIeeeFloatWaveFormat(newSampleRate, channels);
        this.source = source;

        resampler = new WdlResampler();
        resampler.SetMode(true, 2, false);
        resampler.SetFilterParms();
        resampler.SetFeedMode(false); // output driven
        resampler.SetRates(source.WaveFormat.SampleRate, newSampleRate);
    }

	public WdlResamplingSampleProvider(ISampleProvider source, WdlResampler resampler, int newSampleRate)
    {
		channels = source.WaveFormat.Channels;
		outFormat = WaveFormat.CreateIeeeFloatWaveFormat(newSampleRate, channels);
		this.source = source;
		this.resampler = resampler;
	}

	/// <summary>
	/// Reads from this sample provider
	/// </summary>
	public int Read(float[] buffer, int offset, int count)
    {
        float[] inBuffer;
        int inBufferOffset;
        int framesRequested = count / channels;
        int inNeeded = resampler.ResamplePrepare(framesRequested, outFormat.Channels, out inBuffer, out inBufferOffset);
        int inAvailable = source.Read(inBuffer, inBufferOffset, inNeeded * channels) / channels;
        int outAvailable = resampler.ResampleOut(buffer, offset, inAvailable, framesRequested, channels);
        return outAvailable * channels;
    }

    /// <summary>
    /// Output WaveFormat
    /// </summary>
    public WaveFormat WaveFormat
    {
        get { return outFormat; }
    }
}

public class WdlResampler
{
	private const int WDL_RESAMPLE_MAX_FILTERS = 4;
	private const int WDL_RESAMPLE_MAX_NCH = 64;
	private const double PI = 3.1415926535897932384626433832795;

	/// <summary>
	/// Creates a new Resampler
	/// </summary>
	public WdlResampler()
	{
		m_filterq = 0.707f;
		m_filterpos = 0.693f; // .792 ?

		m_sincoversize = 0;
		m_lp_oversize = 1;
		m_sincsize = 0;
		m_filtercnt = 1;
		m_interp = true;
		m_feedmode = false;

		m_filter_coeffs_size = 0;
		m_sratein = 44100.0;
		m_srateout = 44100.0;
		m_ratio = 1.0;
		m_filter_ratio = -1.0;

		Reset();
	}

	/// <summary>
	/// sets the mode
	/// if sinc set, it overrides interp or filtercnt
	/// </summary>
	public void SetMode(bool interp, int filtercnt, bool sinc, int sinc_size = 64, int sinc_interpsize = 32)
	{
		m_sincsize = sinc && sinc_size >= 4 ? sinc_size > 8192 ? 8192 : sinc_size : 0;
		m_sincoversize = (m_sincsize != 0) ? (sinc_interpsize <= 1 ? 1 : sinc_interpsize >= 4096 ? 4096 : sinc_interpsize) : 1;

		m_filtercnt = (m_sincsize != 0) ? 0 : (filtercnt <= 0 ? 0 : filtercnt >= WDL_RESAMPLE_MAX_FILTERS ? WDL_RESAMPLE_MAX_FILTERS : filtercnt);
		m_interp = interp && (m_sincsize == 0);

		//Debug.WriteLine(String.Format("setting interp={0}, filtercnt={1}, sinc={2},{3}\n", m_interp, m_filtercnt, m_sincsize, m_sincoversize));

		if (m_sincsize == 0)
		{
			m_filter_coeffs = new float[0]; //.Resize(0);
			m_filter_coeffs_size = 0;
		}
		if (m_filtercnt == 0)
		{
			m_iirfilter = null;
		}
	}

	/// <summary>
	/// Sets the filter parameters
	/// used for filtercnt>0 but not sinc
	/// </summary>
	public void SetFilterParms(float filterpos = 0.693f, float filterq = 0.707f)
	{
		m_filterpos = filterpos;
		m_filterq = filterq;
	}

	/// <summary>
	/// Set feed mode
	/// </summary>
	/// <param name="wantInputDriven">if true, that means the first parameter to ResamplePrepare will specify however much input you have, not how much you want</param>
	public void SetFeedMode(bool wantInputDriven)
	{
		m_feedmode = wantInputDriven;
	}

	/// <summary>
	/// Reset
	/// </summary>
	public void Reset(double fracpos = 0.0)
	{
		m_last_requested = 0;
		m_filtlatency = 0;
		m_fracpos = fracpos;
		m_samples_in_rsinbuf = 0;
		if (m_iirfilter != null) m_iirfilter.Reset();
	}

	public void SetRates(double rate_in, double rate_out)
	{
		if (rate_in < 1.0) rate_in = 1.0;
		if (rate_out < 1.0) rate_out = 1.0;
		if (rate_in != m_sratein || rate_out != m_srateout)
		{
			m_sratein = rate_in;
			m_srateout = rate_out;
			m_ratio = m_sratein / m_srateout;
		}
	}

	// amount of input that has been received but not yet converted to output, in seconds
	public double GetCurrentLatency()
	{
		double v = ((double)m_samples_in_rsinbuf - m_filtlatency) / m_sratein;

		if (v < 0.0) v = 0.0;
		return v;
	}

	/// <summary>
	/// Prepare
	/// note that it is safe to call ResamplePrepare without calling ResampleOut (the next call of ResamplePrepare will function as normal)
	/// nb inbuffer was WDL_ResampleSample **, returning a place to put the in buffer, so we return a buffer and offset
	/// </summary>
	/// <param name="out_samples">req_samples is output samples desired if !wantInputDriven, or if wantInputDriven is input samples that we have</param>
	/// <param name="nch"></param>
	/// <param name="inbuffer"></param>
	/// <param name="inbufferOffset"></param>
	/// <returns>returns number of samples desired (put these into *inbuffer)</returns>
	public int ResamplePrepare(int out_samples, int nch, out float[] inbuffer, out int inbufferOffset)
	{
		if (nch > WDL_RESAMPLE_MAX_NCH || nch < 1)
		{
			inbuffer = null;
			inbufferOffset = 0;
			return 0;
		}

		int fsize = 0;
		if (m_sincsize > 1)
		{
			fsize = m_sincsize;
		}

		int hfs = fsize / 2;
		if (hfs > 1 && m_samples_in_rsinbuf < hfs - 1)
		{
			m_filtlatency += hfs - 1 - m_samples_in_rsinbuf;

			m_samples_in_rsinbuf = hfs - 1;

			if (m_samples_in_rsinbuf > 0)
			{
				m_rsinbuf = new float[m_samples_in_rsinbuf * nch];
			}
		}

		int sreq = 0;

		if (!m_feedmode) sreq = (int)(m_ratio * out_samples) + 4 + fsize - m_samples_in_rsinbuf;
		else sreq = out_samples;

		if (sreq < 0) sreq = 0;

		again:
		Array.Resize(ref m_rsinbuf, (m_samples_in_rsinbuf + sreq) * nch);

		int sz = m_rsinbuf.Length / ((nch != 0) ? nch : 1) - m_samples_in_rsinbuf;
		if (sz != sreq)
		{
			if (sreq > 4 && (sz == 0))
			{
				sreq /= 2;
				goto again; // try again with half the size
			}
			// todo: notify of error?
			sreq = sz;
		}

		inbuffer = m_rsinbuf;
		inbufferOffset = m_samples_in_rsinbuf * nch;

		m_last_requested = sreq;
		return sreq;
	}

	// if numsamples_in < the value return by ResamplePrepare(), then it will be flushed to produce all remaining valid samples
	// do NOT call with nsamples_in greater than the value returned from resamplerprpare()! the extra samples will be ignored.
	// returns number of samples successfully outputted to out
	public int ResampleOut(float[] outBuffer, int outBufferIndex, int nsamples_in, int nsamples_out, int nch)
	{
		if (nch > WDL_RESAMPLE_MAX_NCH || nch < 1)
		{
			return 0;
		}

		if (m_filtercnt > 0)
		{
			if (m_ratio > 1.0 && nsamples_in > 0) // filter input
			{
				if (m_iirfilter == null) m_iirfilter = new WDL_Resampler_IIRFilter();

				int n = m_filtercnt;
				m_iirfilter.setParms((1.0 / m_ratio) * m_filterpos, m_filterq);

				int bufIndex = m_samples_in_rsinbuf * nch;
				int a, x;
				int offs = 0;
				for (x = 0; x < nch; x++)
					for (a = 0; a < n; a++)
						m_iirfilter.Apply(m_rsinbuf, bufIndex + x, m_rsinbuf, bufIndex + x, nsamples_in, nch, offs++);
			}
		}

		m_samples_in_rsinbuf += Math.Min(nsamples_in, m_last_requested); // prevent the user from corrupting the internal state


		int rsinbuf_availtemp = m_samples_in_rsinbuf;

		if (nsamples_in < m_last_requested) // flush out to ensure we can deliver
		{
			int fsize = (m_last_requested - nsamples_in) * 2 + m_sincsize * 2;

			int alloc_size = (m_samples_in_rsinbuf + fsize) * nch;
			Array.Resize(ref m_rsinbuf, alloc_size);
			if (m_rsinbuf.Length == alloc_size)
			{
				Array.Clear(m_rsinbuf, m_samples_in_rsinbuf * nch, fsize * nch);
				rsinbuf_availtemp = m_samples_in_rsinbuf + fsize;
			}
		}

		int ret = 0;
		double srcpos = m_fracpos;
		double drspos = m_ratio;
		int localin = 0; // localin is an index into m_rsinbuf

		int outptr = outBufferIndex;  // outptr is an index into  outBuffer;

		int ns = nsamples_out;

		int outlatadj = 0;

		if (m_sincsize != 0) // sinc interpolating
		{
			if (m_ratio > 1.0) BuildLowPass(1.0 / (m_ratio * 1.03));
			else BuildLowPass(1.0);

			int filtsz = m_filter_coeffs_size;
			int filtlen = rsinbuf_availtemp - filtsz;
			outlatadj = filtsz / 2 - 1;
			int filter = 0; // filter is an index into m_filter_coeffs m_filter_coeffs.Get();

			if (nch == 1)
			{
				while (ns-- != 0)
				{
					int ipos = (int)srcpos;

					if (ipos >= filtlen - 1) break; // quit decoding, not enough input samples

					SincSample1(outBuffer, outptr, m_rsinbuf, localin + ipos, srcpos - ipos, m_filter_coeffs, filter, filtsz);
					outptr++;
					srcpos += drspos;
					ret++;
				}
			}
			else if (nch == 2)
			{
				while (ns-- != 0)
				{
					int ipos = (int)srcpos;

					if (ipos >= filtlen - 1) break; // quit decoding, not enough input samples

					SincSample2(outBuffer, outptr, m_rsinbuf, localin + ipos * 2, srcpos - ipos, m_filter_coeffs, filter, filtsz);
					outptr += 2;
					srcpos += drspos;
					ret++;
				}
			}
			else
			{
				while (ns-- != 0)
				{
					int ipos = (int)srcpos;

					if (ipos >= filtlen - 1) break; // quit decoding, not enough input samples

					SincSample(outBuffer, outptr, m_rsinbuf, localin + ipos * nch, srcpos - ipos, nch, m_filter_coeffs, filter, filtsz);
					outptr += nch;
					srcpos += drspos;
					ret++;
				}
			}
		}
		else if (!m_interp) // point sampling
		{
			if (nch == 1)
			{
				while (ns-- != 0)
				{
					int ipos = (int)srcpos;
					if (ipos >= rsinbuf_availtemp) break; // quit decoding, not enough input samples

					outBuffer[outptr++] = m_rsinbuf[localin + ipos];
					srcpos += drspos;
					ret++;
				}
			}
			else if (nch == 2)
			{
				while (ns-- != 0)
				{
					int ipos = (int)srcpos;
					if (ipos >= rsinbuf_availtemp) break; // quit decoding, not enough input samples

					ipos += ipos;

					outBuffer[outptr + 0] = m_rsinbuf[localin + ipos];
					outBuffer[outptr + 1] = m_rsinbuf[localin + ipos + 1];
					outptr += 2;
					srcpos += drspos;
					ret++;
				}
			}
			else
				while (ns-- != 0)
				{
					int ipos = (int)srcpos;
					if (ipos >= rsinbuf_availtemp) break; // quit decoding, not enough input samples

					Array.Copy(m_rsinbuf, localin + ipos * nch, outBuffer, outptr, nch);
					outptr += nch;
					srcpos += drspos;
					ret++;
				}
		}
		else // linear interpolation
		{
			if (nch == 1)
			{
				while (ns-- != 0)
				{
					int ipos = (int)srcpos;
					double fracpos = srcpos - ipos;

					if (ipos >= rsinbuf_availtemp - 1)
					{
						break; // quit decoding, not enough input samples
					}

					double ifracpos = 1.0 - fracpos;
					int inptr = localin + ipos;
					outBuffer[outptr++] = (float)(m_rsinbuf[inptr] * (ifracpos) + m_rsinbuf[inptr + 1] * (fracpos));
					srcpos += drspos;
					ret++;
				}
			}
			else if (nch == 2)
			{
				while (ns-- != 0)
				{
					int ipos = (int)srcpos;
					double fracpos = srcpos - ipos;

					if (ipos >= rsinbuf_availtemp - 1)
					{
						break; // quit decoding, not enough input samples
					}

					double ifracpos = 1.0 - fracpos;
					int inptr = localin + ipos * 2;
					outBuffer[outptr + 0] = (float)(m_rsinbuf[inptr] * (ifracpos) + m_rsinbuf[inptr + 2] * (fracpos));
					outBuffer[outptr + 1] = (float)(m_rsinbuf[inptr + 1] * (ifracpos) + m_rsinbuf[inptr + 3] * (fracpos));
					outptr += 2;
					srcpos += drspos;
					ret++;
				}
			}
			else
			{
				while (ns-- != 0)
				{
					int ipos = (int)srcpos;
					double fracpos = srcpos - ipos;

					if (ipos >= rsinbuf_availtemp - 1)
					{
						break; // quit decoding, not enough input samples
					}

					double ifracpos = 1.0 - fracpos;
					int ch = nch;
					int inptr = localin + ipos * nch;
					while (ch-- != 0)
					{
						outBuffer[outptr++] = (float)(m_rsinbuf[inptr] * (ifracpos) + m_rsinbuf[inptr + nch] * (fracpos));
						inptr++;
					}
					srcpos += drspos;
					ret++;
				}
			}
		}

		if (m_filtercnt > 0)
		{
			if (m_ratio < 1.0 && ret > 0) // filter output
			{
				if (m_iirfilter == null) m_iirfilter = new WDL_Resampler_IIRFilter();
				int n = m_filtercnt;
				m_iirfilter.setParms(m_ratio * m_filterpos, m_filterq);

				int x, a;
				int offs = 0;
				for (x = 0; x < nch; x++)
					for (a = 0; a < n; a++)
						m_iirfilter.Apply(outBuffer, x, outBuffer, x, ret, nch, offs++);
			}
		}

		if (ret > 0 && rsinbuf_availtemp > m_samples_in_rsinbuf) // we had to pad!!
		{
			// check for the case where rsinbuf_availtemp>m_samples_in_rsinbuf, decrease ret down to actual valid samples
			double adj = (srcpos - m_samples_in_rsinbuf + outlatadj) / drspos;
			if (adj > 0)
			{
				ret -= (int)(adj + 0.5);
				if (ret < 0) ret = 0;
			}
		}

		int isrcpos = (int)srcpos;
		m_fracpos = srcpos - isrcpos;
		m_samples_in_rsinbuf -= isrcpos;
		if (m_samples_in_rsinbuf <= 0)
		{
			m_samples_in_rsinbuf = 0;
		}
		else
		{
			// TODO: bug here
			Array.Copy(m_rsinbuf, localin + isrcpos * nch, m_rsinbuf, localin, m_samples_in_rsinbuf * nch);
		}



		return ret;
	}

	// only called in sinc modes
	private void BuildLowPass(double filtpos)
	{
		int wantsize = m_sincsize;
		int wantinterp = m_sincoversize;

		if (m_filter_ratio != filtpos ||
			m_filter_coeffs_size != wantsize ||
			m_lp_oversize != wantinterp)
		{
			m_lp_oversize = wantinterp;
			m_filter_ratio = filtpos;

			// build lowpass filter
			int allocsize = (wantsize + 1) * m_lp_oversize;
			Array.Resize(ref m_filter_coeffs, allocsize);
			//int cfout = 0; // this is an index into m_filter_coeffs
			if (m_filter_coeffs.Length == allocsize)
			{
				m_filter_coeffs_size = wantsize;

				int sz = wantsize * m_lp_oversize;
				int hsz = sz / 2;
				double filtpower = 0.0;
				double windowpos = 0.0;
				double dwindowpos = 2.0 * PI / (double)(sz);
				double dsincpos = PI / m_lp_oversize * filtpos; // filtpos is outrate/inrate, i.e. 0.5 is going to half rate
				double sincpos = dsincpos * (double)(-hsz);

				int x;
				for (x = -hsz; x < hsz + m_lp_oversize; x++)
				{
					double val = 0.35875 - 0.48829 * Math.Cos(windowpos) + 0.14128 * Math.Cos(2 * windowpos) - 0.01168 * Math.Cos(6 * windowpos); // blackman-harris
					if (x != 0) val *= Math.Sin(sincpos) / sincpos;

					windowpos += dwindowpos;
					sincpos += dsincpos;

					m_filter_coeffs[hsz + x] = (float)val;
					if (x < hsz) filtpower += val;
				}
				filtpower = m_lp_oversize / filtpower;
				for (x = 0; x < sz + m_lp_oversize; x++)
				{
					m_filter_coeffs[x] = (float)(m_filter_coeffs[x] * filtpower);
				}
			}
			else m_filter_coeffs_size = 0;

		}
	}

	// SincSample(WDL_ResampleSample *outptr, WDL_ResampleSample *inptr, double fracpos, int nch, WDL_SincFilterSample *filter, int filtsz)
	private void SincSample(float[] outBuffer, int outBufferIndex, float[] inBuffer, int inBufferIndex, double fracpos, int nch, float[] filter, int filterIndex, int filtsz)
	{
		int oversize = m_lp_oversize;
		fracpos *= oversize;
		int ifpos = (int)fracpos;
		filterIndex += oversize - 1 - ifpos;
		fracpos -= ifpos;

		for (int x = 0; x < nch; x++)
		{
			double sum = 0.0, sum2 = 0.0;
			int fptr = filterIndex;
			int iptr = inBufferIndex + x;
			int i = filtsz;
			while (i-- != 0)
			{
				sum += filter[fptr] * inBuffer[iptr];
				sum2 += filter[fptr + 1] * inBuffer[iptr];
				iptr += nch;
				fptr += oversize;
			}
			outBuffer[outBufferIndex + x] = (float)(sum * fracpos + sum2 * (1.0 - fracpos));
		}
	}

	// SincSample1(WDL_ResampleSample* outptr, WDL_ResampleSample* inptr, double fracpos, WDL_SincFilterSample* filter, int filtsz)
	private void SincSample1(float[] outBuffer, int outBufferIndex, float[] inBuffer, int inBufferIndex, double fracpos, float[] filter, int filterIndex, int filtsz)
	{
		int oversize = m_lp_oversize;
		fracpos *= oversize;
		int ifpos = (int)fracpos;
		filterIndex += oversize - 1 - ifpos;
		fracpos -= ifpos;

		double sum = 0.0, sum2 = 0.0;
		int fptr = filterIndex;
		int iptr = inBufferIndex;
		int i = filtsz;
		while (i-- != 0)
		{
			sum += filter[fptr] * inBuffer[iptr];
			sum2 += filter[fptr + 1] * inBuffer[iptr];
			iptr++;
			fptr += oversize;
		}
		outBuffer[outBufferIndex] = (float)(sum * fracpos + sum2 * (1.0 - fracpos));
	}

	// SincSample2(WDL_ResampleSample* outptr, WDL_ResampleSample* inptr, double fracpos, WDL_SincFilterSample* filter, int filtsz)
	private void SincSample2(float[] outptr, int outBufferIndex, float[] inBuffer, int inBufferIndex, double fracpos, float[] filter, int filterIndex, int filtsz)
	{
		int oversize = m_lp_oversize;
		fracpos *= oversize;
		int ifpos = (int)fracpos;
		filterIndex += oversize - 1 - ifpos;
		fracpos -= ifpos;

		double sum = 0.0;
		double sum2 = 0.0;
		double sumb = 0.0;
		double sum2b = 0.0;
		int fptr = filterIndex;
		int iptr = inBufferIndex;
		int i = filtsz / 2;
		while (i-- != 0)
		{
			sum += filter[fptr] * inBuffer[iptr];
			sum2 += filter[fptr] * inBuffer[iptr + 1];
			sumb += filter[fptr + 1] * inBuffer[iptr];
			sum2b += filter[fptr + 1] * inBuffer[iptr + 1];
			sum += filter[fptr + oversize] * inBuffer[iptr + 2];
			sum2 += filter[fptr + oversize] * inBuffer[iptr + 3];
			sumb += filter[fptr + oversize + 1] * inBuffer[iptr + 2];
			sum2b += filter[fptr + oversize + 1] * inBuffer[iptr + 3];
			iptr += 4;
			fptr += oversize * 2;
		}
		outptr[outBufferIndex + 0] = (float)(sum * fracpos + sumb * (1.0 - fracpos));
		outptr[outBufferIndex + 1] = (float)(sum2 * fracpos + sum2b * (1.0 - fracpos));
	}

	private double m_sratein; // WDL_FIXALIGN
	private double m_srateout;
	private double m_fracpos;
	private double m_ratio;
	private double m_filter_ratio;
	private float m_filterq, m_filterpos;
	private float[] m_rsinbuf; // WDL_TypedBuf<WDL_ResampleSample>
	private float[] m_filter_coeffs; // WDL_TypedBuf<WDL_SincFilterSample>

	private WDL_Resampler_IIRFilter m_iirfilter; // WDL_Resampler_IIRFilter *

	private int m_filter_coeffs_size;
	private int m_last_requested;
	private int m_filtlatency;
	private int m_samples_in_rsinbuf;
	private int m_lp_oversize;

	private int m_sincsize;
	private int m_filtercnt;
	private int m_sincoversize;
	private bool m_interp;
	private bool m_feedmode;



	class WDL_Resampler_IIRFilter
	{
		public WDL_Resampler_IIRFilter()
		{
			m_fpos = -1;
			Reset();
		}

		public void Reset()
		{
			m_hist = new double[WDL_RESAMPLE_MAX_FILTERS * WDL_RESAMPLE_MAX_NCH, 4];
		}

		public void setParms(double fpos, double Q)
		{
			if (Math.Abs(fpos - m_fpos) < 0.000001) return;
			m_fpos = fpos;

			double pos = fpos * PI;
			double cpos = Math.Cos(pos);
			double spos = Math.Sin(pos);

			double alpha = spos / (2.0 * Q);

			double sc = 1.0 / (1 + alpha);
			m_b1 = (1 - cpos) * sc;
			m_b2 = m_b0 = m_b1 * 0.5;
			m_a1 = -2 * cpos * sc;
			m_a2 = (1 - alpha) * sc;

		}

		public void Apply(float[] inBuffer, int inIndex, float[] outBuffer, int outIndex, int ns, int span, int w)
		{
			double b0 = m_b0, b1 = m_b1, b2 = m_b2, a1 = m_a1, a2 = m_a2;

			while (ns-- != 0)
			{
				double inx = inBuffer[inIndex];
				inIndex += span;
				double outx = (double)(inx * b0 + m_hist[w, 0] * b1 + m_hist[w, 1] * b2 - m_hist[w, 2] * a1 - m_hist[w, 3] * a2);
				m_hist[w, 1] = m_hist[w, 0];
				m_hist[w, 0] = inx;
				m_hist[w, 3] = m_hist[w, 2];
				m_hist[w, 2] = denormal_filter(outx);
				outBuffer[outIndex] = (float)m_hist[w, 2];

				outIndex += span;
			}
		}

		double denormal_filter(float x)
		{
			// TODO: implement denormalisation
			return x;
		}
		double denormal_filter(double x)
		{
			// TODO: implement denormalisation
			return x;
		}

		private double m_fpos;
		private double m_a1, m_a2;
		private double m_b0, m_b1, m_b2;
		private double[,] m_hist;
	}

}